import parole
from parole.colornames import colors
from parole.display import interpolateRGB
import pygame, random
from pprint import pprint

import sim, interface, main
from util import *

# Tweak these
COLS, ROWS = 96, 64
roomsPerLevel = 16

thingsPerLevel = {
    "potions"  : 7,
    "scrolls"  : 5, 
    "weapons"  : 8,
    "armor"    : 6,
    "monsters" : 20,
    "lights"   : 4,
}

def roomBillAt(depth):
    # Figure out what rooms will (try to) be on this map
    manifest = dict(parole.resource.getObject('scripts/rooms/manifest.py',
            'manifest'))

    manDepth = list(manifest[depth])
    roomBill = []

    for (roomName, w) in manDepth:
        if w == 'guaranteed':
            roomClass = parole.resource.getObject('scripts/rooms/%s.py' % roomName,
                    'roomClass')
            roomBill.append((roomClass, 1))
            manDepth.remove((roomName, w))

    for n in xrange(roomsPerLevel-len(roomBill)):
        roomName = chooseWeighted(manDepth)
        roomClass = parole.resource.getObject('scripts/rooms/%s.py' % roomName,
                'roomClass')
        roomBill.append((roomClass, 1))

    return roomBill

def getRandomPlacementTile(map):
    destTile = None
    while not destTile:
        pos = (random.randint(0, COLS-1), random.randint(0, ROWS-1))
        t = map[pos]
        foundFloor = False
        for obj in t:
            if isinstance(obj, Floor):
                foundFloor = True
            elif obj.blocksMove:
                foundFloor = False
                break
        if foundFloor:
            destTile = t
    return destTile

def placeThings(thingType, map, depth):
    manifest = parole.resource.getObject('scripts/%s/manifest.py' % thingType,
            'manifest')
    nplaced = 0
    # place scrolls at any floor location
    for n in xrange(thingsPerLevel[thingType]):
        destTile = getRandomPlacementTile(map)
        thingName = chooseWeighted(manifest[depth])
        if not thingName:
            continue
        thingClass = parole.resource.getObject('scripts/%s/%s.py' % \
                (thingType, thingName), 'thingClass')
        thing = thingClass()
        destTile.add(thing)
        nplaced += 1
    parole.debug('Placed %d of "%s".', nplaced, thingType)


# The generator that fills the level with rock to be carved out into rooms
#rockGenerator = \
#    lambda bg_rgb: parole.map.MapObjectGenerator("rockGenerator",
#        lambda: Rock('solid rock', 100, 
#            parole.map.AsciiTile(' ',
#            (random.randint(0,64),random.randint(0,64),random.randint(0,96)),
#            bg_rgb=bg_rgb), blockLOS=True, blockMove=True, massNoun=True))

def rockGenerator(bg_rgb, name='solid rock', massNoun=True, startsVowel=False):
    return parole.map.MapObjectGenerator("rockGenerator", lambda: Rock(name, 100, 
            parole.map.AsciiTile(' ',
            (random.randint(0,64),random.randint(0,64),random.randint(0,96)),
            bg_rgb=bg_rgb), blockLOS=True, blockMove=True, massNoun=massNoun,
            startsVowel=startsVowel))

def thingGenerator(thingtype, name):
    thingClass = parole.resource.getObject('scripts/%s/%s.py' % (thingtype,
        name), 'thingClass')
    return parole.map.MapObjectGenerator("", lambda: thingClass())

currentDownStair = None

def makeDownStair():
    global currentDownStair

    currentDownStair = sim.Obj('mineshaft down', 100, 
        parole.map.AsciiTile('>', colors['White'], bg_rgb=(127,150,127)),
        blockLOS=False, blockMove=False)
    return currentDownStair

downStairGenerator = \
        parole.map.MapObjectGenerator('', 
                makeDownStair, clearFirst=True)

currentUpStair = None

def makeUpStair():
    global currentUpStair
    currentUpStair = sim.Obj('mineshaft up', 100, 
        parole.map.AsciiTile('<', colors['White'], bg_rgb=(127,150,127)),
        blockLOS=False, blockMove=False)
    return currentUpStair

upStairGenerator = \
        parole.map.MapObjectGenerator('', 
                makeUpStair, clearFirst=True)

def makeLevel(depth):
    global currentDownStair, currentUpStair
    currentDownStair = None
    currentUpStair = None

    parole.debug('Generating dungeon level %d...', depth)
    mbox = interface.messageBox('Creating dungeon level %d...' % depth)
    parole.display.update()

    map = parole.map.Map2D('Dungeon level %d' % depth, (COLS, ROWS))

    # The format is an ordered list of (room, num) tuples, indicating the order
    # in which to lay how many of what kind of rooms.
    roomBill = roomBillAt(depth)

    darkColor = colors['Gray']
    lightColor = colors['LightSteelBlue']
    rockConditions = dict([(i, rockGenerator(interpolateRGB(darkColor,
        lightColor, float(i)/10.))) for i in xrange(10)])
    rockAreaGenerator =  parole.map.CellularAutomataGenerator("rockAreaGenerator",
            0.35, rockConditions, seedEdges=True)
    

    # Generate, place, and connect the rooms requested in the room bill
    Digger.depth = depth
    roomsAndCorridorsGen = parole.map.RoomsAndCorridorsGenerator('dungeon gen',
            rockAreaGenerator, roomBill, Digger, makeFloor, maxConnectDist=45)
    rooms = roomsAndCorridorsGen.apply(map)

    # TODO - avoid singleton rooms
    # place the stairs in one of the rooms
    if not currentDownStair and depth != 6:
        dstairRoom = random.choice(rooms)
        dstairPos = dstairRoom.randomFloor(map)
        makeDownStair()
        map[dstairPos].add(currentDownStair)
    if not currentUpStair:
        ustairRoom = random.choice(rooms)
        ustairPos = ustairRoom.randomFloor(map)
        makeUpStair()
        parole.debug('****** adding up stair to %r', ustairPos)
        map[ustairPos].add(currentUpStair)

    # amount of ambient light in the dungeon 
    if parole.conf.options.wizard:
        map.setAmbientLight((255,255,255), 1.0)
    elif depth == 1:
        map.setAmbientLight((255,255,255), 0.2)
    elif depth == 2:
        map.setAmbientLight((255,255,255), 0.1)
    else:
        map.setAmbientLight((255,255,255), 0.0)

    map.update()

    # place comestibles
    placeThings('scrolls', map, depth)
    placeThings('potions', map, depth)

    # place equipment
    placeThings('armor', map, depth)
    placeThings('weapons', map, depth)
    placeThings('lights', map, depth)

    # place monsters
    placeThings('monsters', map, depth)

    parole.display.scene.remove(mbox)
    parole.display.update()
    
    # return map and player starting pos
    return map, currentUpStair and currentUpStair.pos, \
                currentDownStair and currentDownStair.pos 

class Floor(sim.Obj):
    """
    Any floor should be an instance of this if it wants to be able to have
    items/monsters placed at it.
    """
    pass

class Rock(sim.Obj):
    """
    Class that identifies an object as belonging to the solid rock from which
    the level is carved.
    """
    pass
    
def makeFloor(room):
    """
    Returs a floor object with appropriate naming and coloring for the given
    room.
    """
    return Floor(room.floorName, 0, parole.map.AsciiTile(' ',
        (random.randint(0,64),random.randint(0,64),random.randint(0,96)),
        bg_rgb=interpolateRGB(room.floorColor1, room.floorColor2, room.mixture)))

class Room(object):
    def __init__(self, pos, size, floorColor1, floorColor2, mixture,
                 floorName='dirt floor'):
        self.name = 'unnamed room'
        self.pos = pos
        self.size = size
        self.rect = pygame.Rect(self.pos, self.size)
        self.floorColor1 = floorColor1
        self.floorColor2 = floorColor2
        self.mixture = mixture
        self.floorName = floorName

        self.floorConditions = dict([
            (x, self.__floorGenerator(interpolateRGB(self.floorColor1,
                                                     self.floorColor2,
                                                     float(x)/10.))) \
            for x in xrange(11)
        ])

        # cellular automaton for laying 2-tone floors
        self.floorAreaGenerator = \
            parole.map.CellularAutomataGenerator("brazier floor area",
                    self.mixture, self.floorConditions, clearFirst=True,
                    seedEdges=True)

    def __repr__(self):
        return "Room(%r, %r)" % (self.name, self.rect)

    def __floorGenerator(self, bg_rgb):
        return parole.map.MapObjectGenerator("twotone floor",
            lambda: Floor(self.floorName, 0,
                parole.map.AsciiTile(' ', (random.randint(0,64),
                random.randint(0,64), random.randint(0,96)), bg_rgb=bg_rgb)))

    # Default diggable squares on perimeter -- should be overridden for
    # non-rectangular rooms
    def diggableOut(self):
        return [p for p in perimeter(self.rect) if p not in corners(self.rect)]

    def diggableIn(self):
        return [p for p in perimeter(self.rect) if p not in corners(self.rect)]

    def apply(self, map):
        raise NotImplementedError('oy vey!')

    def randomFloor(self, map):
        ntries = 1000
        while ntries:
            ntries -= 1
            x = random.randint(self.rect.left, self.rect.right)
            y = random.randint(self.rect.top, self.rect.top)

            try:
                tile = map[x,y]
            except IndexError:
                continue

            if not tile.hasMoveBlocker():
                return x,y

        #ookay. just guess the center
        return (self.rect.centerx+1, self.rect.centery+1)


class TemplateRoom(Room):
    def __init__(self, template, legend, diggable, pos, floorColor1,
            floorColor2, mixture, floorName='dirt floor'):
        self.template = template
        self.legend = legend
        self.diggable = diggable
        self.templateGen = parole.map.TemplateGenerator('template room',
                template, legend)

        # calculate size from template
        w = max([len(row) for row in self.templateGen.templateRows])
        size = (w, len(self.templateGen.templateRows))

        Room.__init__(self, pos, size, floorColor1, floorColor2, mixture,
                floorName)

        self.templateGen.backgroundGen = self.floorAreaGenerator

        p = list(perimeter(self.rect))
        for (x,y) in self.diggableOut():
            if (x,y) not in p:
                parole.warn('%r: diggableOut %r off perimeter.', self, (x,y))
        for (x,y) in self.diggableIn():
            if (x,y) not in p:
                parole.warn('%r: diggableIn %r off perimeter.', self, (x,y))

    def apply(self, map):
        self.templateGen.apply(map, self.rect)

    def diggableOut(self):
        if self.diggable:
            return [self.rect.move(x, y).topleft for x,y in self.diggable]
        return [p for p in perimeter(self.rect) if p not in corners(self.rect)]

    def diggableIn(self):
        if self.diggable:
            return [self.rect.move(x, y).topleft for x,y in self.diggable]
        return [p for p in perimeter(self.rect) if p not in corners(self.rect)]

class Digger(object):
    depth = 1

    def __init__(self):
        self.alreadyDoored = []

    def door(self):
        if random.random() > 0.5:
            return sim.Door('wooden door', 100, parole.map.AsciiTile('/',
             colors['SaddleBrown']), parole.map.AsciiTile('+',
             colors['SaddleBrown']))

        return sim.Door('iron door', 100, parole.map.AsciiTile('/',
             colors['DimGray']), parole.map.AsciiTile('+',
             colors['DimGray']), startsVowel=True)

    def digTile(self, map, tile, srcRoom, destRoom, allRooms):
        destPerim = list(perimeter(destRoom.rect))
        hasFloor = False
        for obj in list(tile):
            if isinstance(obj, Rock):
                tile.remove(obj)
            elif isinstance(obj, Floor):
                hasFloor = True
        if not hasFloor:
            tile.add(makeFloor(srcRoom))
    
        def addDoor():
            if self.depth < 5:
                # doors are only possible on the bottom two levels
                return
            for n in neighbors((tile.col, tile.row)):
                if n[0] < 0 or n[1] < 0 or n[0] >= map.cols or n[1] >= map.rows:
                    continue
                for obj in map[n]:
                    if isinstance(obj, sim.Door):
                        return
            tile.add(self.door())
    
        if (tile.col, tile.row) in destPerim:
            if destRoom not in self.alreadyDoored:
                #tile.add(door())
                addDoor()
                self.alreadyDoored.append(destRoom)
            if (tile.col, tile.row) in corners(destRoom.rect):
                return True
            if hasFloor:
                return False # stop here, we've reached the room
            else:
                return True
    
        for otherRoom in allRooms:
            if (otherRoom not in self.alreadyDoored) and \
                    (tile.col, tile.row) in list(perimeter(otherRoom.rect)):
                addDoor()
                self.alreadyDoored.append(otherRoom)
    
        return True


class Brazier(sim.LightSource):
    def __init__(self):
        super(Brazier, self).__init__('brazier', 70,
                parole.map.AsciiTile('*', colors['Gold']), 50, 10000,
                (random.randint(200,255),
                 random.randint(200,255),
                 random.randint(200,255)),
                3.0 + 2.0*random.random(),
                15*60000)
        if random.random() < 0.08:
            main.schedule.add(sim.Event('activate light', main.schedule.time,
                (self,), dests=[self]))

